package com.danicsoft.daide.token;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Checks the syntax of a TokenGroup based on the syntax file specified.
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class SyntaxChecker {

	/**
	 * Encapsultes a line in the syntax document. Each line consists of an ID
	 * which can be used to identify it, the syntax level it is allowed at and
	 * the rule for what is allowed inside of it.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	private class SyntaxRule {
		/**
		 * The ID of this rule. Should be a two digit hexadecimal string.
		 */
		public String ID;

		/**
		 * The syntax level this rule is allowed at.
		 */
		public int level;

		/**
		 * The acceptable contents of this rule type.
		 */
		public List<String> subRules = new ArrayList<String>();

		/**
		 * Constructs a new SyntaxRule based on the next non-empty line.
		 * 
		 * @param reader
		 *        The reader to read lines from
		 * @throws IOException
		 *         If we could not read from the reader, or we encountered the
		 *         end of the file.
		 */
		public SyntaxRule(LineNumberReader reader) throws IOException {
			String thisLine;
			String[] toks;
			do {
				thisLine = reader.readLine();
				toks = thisLine.split(" |\t");
			} while (thisLine.length() == 0 || toks.length < 2);

			ID = toks[0];
			level = Integer.parseInt(toks[1]);
			for (int i = 2; i < toks.length; i++) {
				if (toks[i].startsWith(";"))
					break;
				if (toks[i].length() > 0)
					subRules.add(toks[i]);
			}

		}

		@Override
		public String toString() {
			return ID + ' ' + level + ' ' + subRules;
		}
	}

	/**
	 * The rules for this syntax checker. This is a map from ID to a list of all
	 * rules that have this ID.
	 */
	private Map<String, List<SyntaxRule>> rules = new HashMap<String, List<SyntaxRule>>();

	/**
	 * Consutrct a new SyntaxChecker from the given file.
	 * 
	 * @param syntaxFile
	 *        The file to load the syntax defintion from
	 * @throws IOException
	 *         If we could not read from the file.
	 * @throws FileNotFoundException
	 *         If the file did not exist
	 */
	public SyntaxChecker(File syntaxFile) throws IOException, FileNotFoundException {
		InputStream iStream = new FileInputStream(syntaxFile);
		Reader reader = new InputStreamReader(iStream);
		LineNumberReader lineReader = new LineNumberReader(reader);
		try {
			while (true) {
				SyntaxRule rule = new SyntaxRule(lineReader);
				List<SyntaxRule> ruleList = rules.get(rule.ID);
				if (ruleList == null) {
					ruleList = new ArrayList<SyntaxRule>();
					rules.put(rule.ID, ruleList);
				}
				ruleList.add(rule);
			}
		}
		catch (NullPointerException e) {

		}
	}

	/**
	 * Checks if a TokenGroup is valid according to allowable syntax.
	 * 
	 * @param tokens
	 *        The TokenGroup to check.
	 * @param syntaxLevel
	 *        The syntax level allowable.
	 * @return True if the group is valid, false otherwise.
	 */
	public boolean isValid(TokenGroup tokens, int syntaxLevel) {
		return isGroupValid(tokens, syntaxLevel, "00");
	}

	/**
	 * Checks to see if a group of a given ID is valid.
	 * 
	 * @param tokens
	 *        The tokens to check.
	 * @param syntaxLevel
	 *        The syntax level allowble
	 * @param groupID
	 *        The ID of the rule we are checking against.
	 * @return True if the group is valid, false otherwise.
	 */
	private boolean isGroupValid(TokenGroup tokens, int syntaxLevel, String groupID) {
		List<SyntaxRule> ruleList = rules.get(groupID);
		for (SyntaxRule sRule : ruleList) {
			if (matchesRule(sRule, (TokenGroup) tokens.clone(), syntaxLevel)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns true if the given token group matches the TokenGroup given.
	 * 
	 * @param rule
	 *        The rule to check against
	 * @param group
	 *        The group to check
	 * @param syntaxLevel
	 *        The syntax level allowable.
	 * @return True if the group matched the rule, false otherwise.
	 */
	private boolean matchesRule(SyntaxRule rule, TokenGroup group, int syntaxLevel) {
		if (rule.level > syntaxLevel) {
			return false;
		}
		boolean repeatLast = false;
		String last = "";
		Iterator<String> subRules = rule.subRules.iterator();
		for (Token nextToken : group) {
			String rulePart;
			if (subRules.hasNext())
				rulePart = subRules.next();
			else {
				if (!repeatLast) {
					return false;
				}
				rulePart = last;
			}
			if (rulePart.startsWith("[sub")) {
				if (!nextToken.isGroup()) {
					return false;
				}
				String subType = rulePart.substring(4, 6);
				if (!isGroupValid((TokenGroup) nextToken, syntaxLevel, subType)) {
					return false;
				}
			}
			else if (rulePart.startsWith("[repeat")) {
				repeatLast = true;
			}
			else if (rulePart.startsWith("[string")) {
				if (nextToken.getType() != TokenMap.TEXT) {
					return false;
				}
			}

			else if (rulePart.startsWith("[number")) {
				if (nextToken.getType() != TokenMap.INTEGER) {
					return false;
				}
			}
			else if (rulePart.startsWith("[province")) {
				if (!TokenMap.provinces.isOfType(nextToken)) {
					return false;
				}
			}
			else if (rulePart.startsWith("[token")) {
				if (!nextToken.isGroup()) {
					return false;
				}
				String subType = rulePart.substring(4, 6);
				if (!isGroupValid(group, syntaxLevel, subType)) {
					return false;
				}
			}
			else if (rulePart.startsWith("#")) {

			}
			else {
				if (!nextToken.toString().equals(rulePart)) {
					return false;
				}
			}
			last = rulePart;
		}
		return true;
	}

	/**
	 * Run a test of a given string. The string to test and the file to load the
	 * syntax from are both hardcoded here.
	 * 
	 * @param args
	 *        Ignored.
	 */
	public static void main(String[] args) {
		try {
			String testString = "HLO (ENG) (1234) ((LVL 20) (MTL 1200))";
			SyntaxChecker checker = new SyntaxChecker(new File("C:\\Documents and Settings\\Daniel\\Desktop\\Diplomacy\\Mapper\\language_encoding.txt"));
			TokenParser parser = new TokenParser(new TokenMap(), null, null);
			TokenGroup group = parser.readFromText(testString);
			if (checker.isValid(group, 0)) {
				System.out.println("Valid!");
			}
			else {
				System.out.println("Not Valid!");
			}
		}
		catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		catch (TextException e) {
			e.printStackTrace();
		}
	}
}
